package cn.tataben.database;

import android.os.Build;
import android.text.TextUtils;

import cn.tataben.database.selector.Selector;
import cn.tataben.database.table.Column;
import cn.tataben.database.table.Table;
import cn.tataben.database.util.CursorUtils;
import cn.tataben.database.util.SqlUtils;
import com.tencent.wcdb.Cursor;
import com.tencent.wcdb.database.SQLiteDatabase;
import com.tencent.wcdb.database.SQLiteStatement;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;

public final class DB implements Closeable {

    private SQLiteDatabase database;

    /**
     * 数据库实例
     * @param dbConfig
     */
    public DB(Config dbConfig) {
        // 创建并打开数据库
        openOrCreateDatabase(dbConfig);

        // 开启WAL, 对写入加速提升巨大
        database.enableWriteAheadLogging();

        // 升级数据库版本监听
        int oldVersion = database.getVersion();
        int newVersion = dbConfig.getDbVersion();
        if (oldVersion != newVersion) {
            if (oldVersion != 0) {
                UpgradeListener upgradeListener = dbConfig.getDbUpgradeListener();
                if (upgradeListener != null) {
                    upgradeListener.onUpgrade(this, oldVersion, newVersion);
                }
            }else{
                CreateListener createListener = dbConfig.getDbCreateListener();
                if (createListener != null) {
                    createListener.onCreated(this);
                }
            }
            database.setVersion(newVersion);
        }
    }
    /**
     * 创建并打开数据库
     * @param dbConfig
     * @return
     */
    private void openOrCreateDatabase(Config dbConfig) {
        File dbDir = dbConfig.getDbDir();
        if (dbDir != null && (dbDir.exists() || dbDir.mkdirs())) {
            File dbFile = new File(dbDir, dbConfig.getDbName());
            this.database = SQLiteDatabase.openOrCreateDatabase(dbFile, dbConfig.getPassword().getBytes(), null, null);
        }
    }

    /**
     * 关闭数据库
     * @throws IOException
     */
    @Override
    public void close() throws IOException {
        this.database.close();
        this.database = null;
    }

    // TODO 类似的Hebinate对象式SQL
    public void saveOrUpdate(Object entity) throws Exception {
        if (entity instanceof List) {
            List<?> entities = (List<?>) entity;
            if (entities.isEmpty()) {
                return;
            }
            Table<?> table = this.getTable(entities.get(0).getClass());
            for (Object item : entities) {
                saveOrUpdateWithoutTransaction(table, item);
            }
        } else {
            Table<?> table = this.getTable(entity.getClass());
            saveOrUpdateWithoutTransaction(table, entity);
        }
    }
    private void saveOrUpdateWithoutTransaction(Table<?> table, Object entity) throws Exception {
        Column id = table.getId();
        Object idValue = id.getColumnValue(entity);
        if (idValue != null) {
            if (findById(table.getEntityType(), idValue) == null){
                saveBindingIdWithoutTransaction(table, entity);
            }else{
                execUpdate(SqlUtils.buildUpdateSqlInfo(table, entity, database));
            }
        } else {
            saveBindingIdWithoutTransaction(table, entity);
        }
    }
    private boolean saveBindingIdWithoutTransaction(Table<?> table, Object entity) throws Exception {
        Column id = table.getId();
        if (id.isAutoId()) {
            execUpdate(SqlUtils.buildInsertSqlInfo(table, entity, database));
            long idValue = getLastAutoIncrementId(table);
            if (idValue == -1) {
                return false;
            }
            id.setAutoIdValue(entity, idValue);
            return true;
        } else {
            execUpdate(SqlUtils.buildInsertSqlInfo(table, entity, database));
            return true;
        }
    }
    private long getLastAutoIncrementId(Table<?> table) throws Exception {
        long id = -1;
        Cursor cursor = execQuery(SqlUtils.buildLastId(table));
        if (cursor != null) {
            try {
                if (cursor.moveToNext()) {
                    id = cursor.getLong(0);
                }
            } catch (Throwable e) {
                throw new Exception(e);
            } finally {
                CursorUtils.closeQuietly(cursor);
            }
        }
        return id;
    }

    /**
     * 根据ID删除
     * @param entityType
     * @param idValue
     * @throws Exception
     */
    public void deleteById(Class<?> entityType, Object idValue) throws Exception {
        Table<?> table = this.getTable(entityType);
        if (!tableIsExist(table)) {
            return;
        }
        execUpdate(SqlUtils.buildDeleteSqlInfoById(table, idValue));
    }
    /**
     * 删除
     * @param entity
     * @throws Exception
     */
    public void deleteWithoutTransaction(Object entity) throws Exception {
        if (entity instanceof List) {
            List<?> entities = (List<?>) entity;
            if (entities.isEmpty()) {
                return;
            }
            Table<?> table = this.getTable(entities.get(0).getClass());
            if (!tableIsExist(table)) {
                return;
            }
            for (Object item : entities) {
                execUpdate(SqlUtils.buildDeleteSqlInfo(table, item));
            }
        } else {
            Table<?> table = this.getTable(entity.getClass());
            if (!tableIsExist(table)) {
                return;
            }
            execUpdate(SqlUtils.buildDeleteSqlInfo(table, entity));
        }
    }
    /**
     * 清空表
     * @param entityType
     * @throws Exception
     */
    public void clearWithoutTransaction(Class<?> entityType)throws Exception{
        execUpdate(SqlUtils.buildClearSqlInfo(this.getTable(entityType)));
    }

    /**
     * 根据ID查询
     * @param entityType
     * @param idValue
     * @param <T>
     * @return
     * @throws Exception
     */
    public <T> T findById(Class<T> entityType, Object idValue) throws Exception {
        return selector(entityType).where(getTable(entityType).getId().getName(), "=", idValue).find();
    }

    /**
     * 查询单个实体
     * @param entityType
     * @param <T>
     * @return
     * @throws Exception
     */
    public <T> T find(Class<T> entityType)throws Exception{
        return selector(entityType).find();
    }
    /**
     * 查询多个实体
     * @param entityType
     * @param <T>
     * @return
     * @throws Exception
     */
    public <T> List<T> findAll(Class<T> entityType) throws Exception {
        return selector(entityType).findAll();
    }
    /**
     * 条件查询
     * @param entityType
     * @param <T>
     * @return
     * @throws Exception
     */
    public <T> Selector<T> selector(Class<T> entityType) throws Exception {
        return Selector.from(this.getTable(entityType), this);
    }

    // TODO 事务
    public void beginTransaction() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && database.isWriteAheadLoggingEnabled()) {
            database.beginTransactionNonExclusive();
        } else {
            database.beginTransaction();
        }
    }
    public void endTransaction() {
        database.endTransaction();
    }
    public void setTransactionSuccessful() {
        database.setTransactionSuccessful();
    }

    // TODO SQL语句执行
    public Cursor execQuery(String sql) throws Exception {
        try {
            return database.rawQuery(sql, null);
        } catch (Throwable e) {
            throw new Exception(e);
        }
    }
    public void execUpdate(String sql) throws Exception {
        try {
            database.execSQL(sql);
        } catch (Throwable e) {
            throw new Exception(e);
        }
    }
    public void execUpdate(SQLiteStatement statement) throws Exception {
        try {
            statement.execute();
        } catch (Throwable e) {
            throw new Exception(e);
        } finally {
            if (statement != null) {
                try {
                    statement.releaseReference();
                } catch (Throwable ex) {
                    Logger.e(ex.getMessage(), ex);
                }
            }
        }
    }

    // TODO 数据库及表操作
    /** 表缓存 */
    private final HashMap<Class<?>, Table<?>> tableCache = new HashMap<>();
    /**
     * 表是否存在
     * @param table
     * @param <T>
     * @return
     * @throws Exception
     */
    private <T> boolean tableIsExist(Table<T> table)throws Exception{
        if (table.isCheckedDatabase()) {
            return true;
        }
        Cursor cursor = this.execQuery("SELECT COUNT(*) AS c FROM sqlite_master WHERE type='table' AND name='" + table.getName() + "'");
        if (cursor != null) {
            try {
                if (cursor.moveToNext()) {
                    int count = cursor.getInt(0);
                    if (count > 0) {
                        table.setCheckedDatabase(true);
                        return true;
                    }
                }
            } catch (Throwable e) {
                throw new Exception(e);
            } finally {
                CursorUtils.closeQuietly(cursor);
            }
        }
        return false;
    }
    /**
     * 从缓存中获取表实例
     * @param entityType
     * @param <T>
     * @return
     * @throws Exception
     */
    public <T> Table<T> getTable(Class<T> entityType) throws Exception {
        synchronized (tableCache) {
            Table<T> table = (Table<T>) tableCache.get(entityType);
            if (table == null) {
                try {
                    table = new Table<>(entityType);
                } catch (Throwable ex) {
                    throw new Exception(ex);
                }
                tableCache.put(entityType, table);
            }
            return table;
        }
    }
    /**
     * 是否是表对象
     * @param entityType
     * @return
     */
    public boolean isTable(Class<?> entityType){
        synchronized (tableCache) {
            return tableCache.get(entityType) != null;
        }
    }
    /***
     * 添加表字段
     * @param entityType
     * @param column
     * @throws Exception
     */
    public void addColumn(Class<?> entityType, String column) throws Exception {
        Table<?> table = this.getTable(entityType);
        Column col = table.getColumnMap().get(column);
        if (col != null) {
            StringBuilder builder = new StringBuilder();
            builder.append("ALTER TABLE ").append("\"").append(table.getName()).append("\"").
                    append(" ADD COLUMN ").append("\"").append(col.getName()).append("\"").
                    append(" ").append(col.getColumnDbType()).
                    append(" ").append(col.getProperty());
            execUpdate(builder.toString());
        }
    }
    /**
     * 创建表
     * @param table
     * @throws Exception
     */
    public void createTable(Table<?> table) throws Exception {
        if (!tableIsExist(table)) {
            synchronized (table.getClass()) {
                if (!tableIsExist(table)) {
                    execUpdate(SqlUtils.buildCreateTableSqlInfo(table));
                    String execAfterTableCreated = table.getOnCreated();
                    if (!TextUtils.isEmpty(execAfterTableCreated)) {
                        execUpdate(execAfterTableCreated);
                    }
                    table.setCheckedDatabase(true);
                }
            }
        }
    }

    // TODO 数据库监听接口
    /**
     * 数据库更新-监听
     */
    public interface UpgradeListener {
        void onUpgrade(DB db, int oldVersion, int newVersion);
    }

    /**
     * 数据库创建-监听
     */
    public interface CreateListener {
        void onCreated(DB db);
    }

    /**
     * 数据库创建配置
     * @author tataben
     */
    public static class Config {
        private File dbDir;
        private int dbVersion = 1;
        private String password = "";
        private String dbName = "mmj.db";
        private CreateListener createListener;
        private UpgradeListener upgradeListener;

        public Config setDbDir(File dbDir) {
            this.dbDir = dbDir;
            return this;
        }
        public Config setDbName(String dbName) {
            if (!TextUtils.isEmpty(dbName)) {
                this.dbName = dbName;
            }
            return this;
        }
        public Config setPassword(String password){
            if (!TextUtils.isEmpty(password)){
                this.password = password;
            }
            return this;
        }
        public Config setDbVersion(int dbVersion) {
            this.dbVersion = dbVersion;
            return this;
        }
        public Config setDbCreateListener(CreateListener createListener) {
            this.createListener = createListener;
            return this;
        }
        public Config setDbUpgradeListener(UpgradeListener upgradeListener) {
            this.upgradeListener = upgradeListener;
            return this;
        }

        public File getDbDir() {
            return dbDir;
        }
        public String getDbName() {
            return dbName;
        }
        public int getDbVersion() {
            return dbVersion;
        }
        public String getPassword(){
            return password;
        }
        public CreateListener getDbCreateListener() {
            return createListener;
        }
        public UpgradeListener getDbUpgradeListener() {
            return upgradeListener;
        }

        @Override
        public int hashCode() {
            int result = dbName.hashCode();
            result = 31 * result + (dbDir != null ? dbDir.hashCode() : 0);
            return result;
        }
        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Config dbConfig = (Config) o;
            if (!dbName.equals(dbConfig.dbName)) {
                return false;
            }
            return dbDir == null ? dbConfig.dbDir == null : dbDir.equals(dbConfig.dbDir);
        }
        @Override
        public String toString() {
            return String.valueOf(dbDir) + "/" + dbName;
        }
    }
}
